// 链条构建 链条执行  
// 做了两件事：构建请求 / 响应拦截器链条 和 通过 Promise 链执行整个请求流程
import {
  AxiosPromise, AxiosRequestConfig, AxiosResponse,
  Method, ResolvedFn, RejectedFn
} from "../types";
import dispatchRequest, { transformURL } from "./dispatchRequest";
import InterceptorManager from "./InterceptorManager";
import mergeConfig from "./mergeConfig";


interface interceptors {
  request: InterceptorManager<AxiosRequestConfig>
  response: InterceptorManager<AxiosResponse>
}

interface PromiseChain<T> {
  resolved: ResolvedFn<T> | ((config: AxiosRequestConfig) => AxiosPromise)
  rejected?: RejectedFn
}

export default class Axios {
  defaults: AxiosRequestConfig
  interceptors: interceptors// 存储请求/响应拦截器管理器

  constructor(initConfig: AxiosRequestConfig) {
    this.defaults = initConfig
    this.interceptors = {  // 创建两个拦截器队列，分别存储请求 / 响应的预处理函数   和 “队列” 数据结构（FIFO）唯一的关系是：响应拦截器的执行顺序符合 FIFO，但请求拦截器是 LIFO（栈行为）。
      request: new InterceptorManager<AxiosRequestConfig>(),
      response: new InterceptorManager<AxiosResponse>()
    }
  }

  request(url: any, config?: any): AxiosPromise {
    // 1. 统一参数格式（兼容 url+config 或 config 直接传入）
    if (typeof url === 'string') { // 调用方式：request(url, config)  axios.request('/user', { method: 'get', timeout: 5000 });
      if (!config) { // config 不存在 初始化空配置对象
        config = {}
      }
      config.url = url // 将url存入配置对象
    }
    else {  // // 场景2：第一个参数不是字符串，直接视为config对象!!!!!!
      // 调用方式：request(config)   axios.request({ url: '/user', method: 'get', timeout: 5000 });
      config = url // 直接使用传入的config
    }

    config = mergeConfig(this.defaults, config)// 合并默认配置和用户配置
    config.method = config.method.toLowerCase()// 统一方法名小写（如 GET → get）

    // 2. 初始化链条：终点是 dispatchRequest（实际发送请求）
    const chain: PromiseChain<any>[] = [{
      resolved: dispatchRequest,
      rejected: undefined
    }]

    // 添加请求拦截器到链条头部 （（逆序执行：后注册的先执行）
    this.interceptors.request.forEach(interceptor => {
      chain.unshift(interceptor) // 拦截器插入到数组开头，头部插入，后注册的拦截器排在链条前面
    })

    // 添加响应拦截器到链条尾部（（顺序执行：先注册的先执行））
    this.interceptors.response.forEach(interceptor => {
      chain.push(interceptor)// 拦截器追加到数组末尾，先注册的拦截器排在链条前面
    })

    // 5. 执行链条：通过 Promise.then() 串联所有节点
    let promise = Promise.resolve(config)// 初始Promise包裹请求配置

    while (chain.length) {// 循环处理链条中的每个节点
      const { resolved, rejected } = chain.shift()!// 取出链条头部的节点（先进先出）
      promise = promise.then(resolved, rejected)
    }

    return promise
  }

  // 以下是HTTP方法封装（便捷调用）
  get(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('get', url, config)
  }

  delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('delete', url, config)
  }

  head(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('head', url, config)
  }

  options(url: string, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithoutData('options', url, config)
  }

  post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('post', url, data, config)
  }

  put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('put', url, data, config)
  }

  patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
    return this._requestMethodWithData('patch', url, data, config)
  }

  getUri(config?: AxiosRequestConfig): string {
    config = mergeConfig(this.defaults, config)
    return transformURL(config)
  }

  // 通用方法：处理无请求体的请求（GET/DELETE/HEAD/OPTIONS）
  _requestMethodWithoutData(
    method: Method,
    url: string,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    // 合并配置：传入的config + 当前method和url（无data字段）
    return this.request(
      Object.assign(config || {}, {
        method,
        url
      })
    )
  }

  // 通用方法：处理有请求体的请求（POST/PUT/PATCH）
  _requestMethodWithData(
    method: Method,
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise {
    // 合并配置：传入的config + 当前method、url和data（包含data字段）
    return this.request(
      Object.assign(config || {}, {
        method,
        url,
        data
      })
    )
  }
}
